Si ejecuta la siguiente declaración en Python 3.7, (de mi prueba) imprimirá b :
if None.__eq__("a"): print("b") Sin embargo, None.__eq__("a") se evalúa como NotImplemented .
Naturalmente, "a".__eq__("a") se evalúa como True , y "b".__eq__("a") se evalúa como False .
Inicialmente descubrí esto al probar el valor de retorno de una función, pero no devolví nada en el segundo caso, por lo que la función devolvió None .
¿Que está pasando aqui?
Este es un gran ejemplo de por qué los métodos __dunder__ no deben usarse directamente, ya que a menudo no son reemplazos apropiados para sus operadores equivalentes; en su lugar, debe usar el operador == para comparaciones de igualdad, o en este caso especial, al verificar None , use is (salte al final de la respuesta para obtener más información).
Has hecho
None.__eq__('a') # NotImplemented Que devuelve NotImplemented ya que los tipos que se comparan son diferentes. Considere otro ejemplo en el que dos objetos con diferentes tipos se comparan de esta manera, como 1 y 'a' . Hacer (1).__eq__('a') tampoco es correcto y devolverá NotImplemented . La forma correcta de comparar estos dos valores para la igualdad sería
1 == 'a' # FalseLo que pasa aquí es
(1).__eq__('a') , que devuelve NotImplemented . Esto indica que la operación no es compatible, por lo que'a'.__eq__(1) , que también devuelve el mismo NotImplemented . Asi que,False .Aquí hay un pequeño y agradable MCVE que usa algunas clases personalizadas para ilustrar cómo sucede esto:
class A: def __eq__(self, other): print('A.__eq__') return NotImplemented class B: def __eq__(self, other): print('B.__eq__') return NotImplemented class C: def __eq__(self, other): print('C.__eq__') return True a = A() b = B() c = C() print(a == b) # A.__eq__ # B.__eq__ # False print(a == c) # A.__eq__ # C.__eq__ # True print(c == a) # C.__eq__ # True Por supuesto, eso no explica por qué la operación devuelve verdadero. Esto se debe a que NotImplemented es en realidad un valor real:
bool(None.__eq__("a")) # TrueIgual que,
bool(NotImplemented) # True Para obtener más información sobre qué valores se consideran verdaderos y falsos, consulte la sección de documentos sobre Pruebas de valor de verdad , así como esta respuesta . Vale la pena señalar aquí que NotImplemented es cierto, pero habría sido una historia diferente si la clase hubiera definido un método __bool__ o __len__ que devolviera False o 0 respectivamente.
Si desea el equivalente funcional del operador == , use operator.eq :
import operator operator.eq(1, 'a') # False Sin embargo, como se mencionó anteriormente, para este escenario específico , donde está verificando None , el uso is :
var = 'a' var is None # False var2 = None var2 is None # True El equivalente funcional de esto es usar operator.is_ :
operator.is_(var2, None) # True None es un objeto especial y solo existe 1 versión en la memoria en cualquier momento. IOW, es el único singleton de la clase NoneType (pero el mismo objeto puede tener cualquier número de referencias). Las pautas de PEP8 lo hacen explícito:
Las comparaciones con singletons como
Nonesiempre deben hacerse conisois not, nunca con los operadores de igualdad.
En resumen, para singletons como None , una verificación de referencia con is es más apropiada, aunque tanto == como is funcionarán bien.
El resultado que está viendo es causado por el hecho de que
None.__eq__("a") # evaluates to NotImplemented se evalúa como NotImplemented , y el valor de verdad de NotImplemented se documenta como True :
https://docs.python.org/3/library/constants.html
Valor especial que deben devolver los métodos especiales binarios (por ejemplo,
__eq__(),__lt__(),__add__(),__rsub__(), etc.) para indicar que la operación no está implementada con respecto al otro tipo; puede ser devuelto por los métodos especiales binarios en el lugar (por ejemplo,__imul__(),__iand__(), etc.) para el mismo propósito. Su valor de verdad es verdadero.
Si llama al __eq()__ manualmente en lugar de solo usar == , debe estar preparado para lidiar con la posibilidad de que devuelva NotImplemented y que su valor de verdad sea verdadero.
Como ya pensó que None.__eq__("a") se evalúa como NotImplemented sin embargo, si intenta algo como
if NotImplemented: print("Yes") else: print("No")el resultado es
sí
esto significa que el valor de verdad de NotImplemented true
Por lo tanto, el resultado de la pregunta es obvio:
None.__eq__(something) produce NotImplemented
Y bool(NotImplemented) evalúa como True
Entonces, if None.__eq__("a") es siempre True